import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios";
import axios from "axios";

// import { ElMessage } from "element-plus";

/**
 * @description: 校验网络请求状态码
 * @param {Number} status
 * @return void
 */
// const checkStatus = (status: number) => {
//     switch (status) {
//         case 400:
//             ElMessage.error("请求失败！请您稍后重试");
//             break;
//         case 401:
//             ElMessage.error("登录失效！请您重新登录");
//             break;
//         case 403:
//             ElMessage.error("当前账号无权限访问！");
//             break;
//         case 404:
//             ElMessage.error("你所访问的资源不存在！");
//             break;
//         case 405:
//             ElMessage.error("请求方式错误！请您稍后重试");
//             break;
//         case 408:
//             ElMessage.error("请求超时！请您稍后重试");
//             break;
//         case 500:
//             ElMessage.error("服务异常！");
//             break;
//         case 502:
//             ElMessage.error("网关错误！");
//             break;
//         case 503:
//             ElMessage.error("服务不可用！");
//             break;
//         case 504:
//             ElMessage.error("网关超时！");
//             break;
//         default:
//             ElMessage.error("请求失败！");
//     }
// };

interface MyAxiosRequestConfig<TReqData = any> extends AxiosRequestConfig<TReqData> {
    interceptors?: {
        reqInterceptorOnFulfilled?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
        reqInterceptorOnRejected?: (err: AxiosError) => any;
        resInterceptorOnFulfilled?: (res: AxiosResponse) => AxiosResponse;
        resInterceptorOnRejected?: (err: AxiosError) => any;
    };
    showLoading?: boolean;
}

export interface ReturnType<TResData = any> {
    code: number;
    data: TResData;
    msg?: string;
    // message?: string;
}

/**
 * A wrapper class for making HTTP requests using Axios.
 * @class
 * @example
 * // Usage example:
 * const lcRequest = new LcRequest(config);
 * lcRequest.get<TReqData, TResData, TResType>({ url: '/users/1' })
 *   .then(response => {
 *     console.log(response.name); // logs the name of the user
 *   })
 *   .catch(error => {
 *     console.error(error);
 *   });
 *
 * @property {AxiosInstance} instance - The Axios instance used for making requests.
 */
class LcRequest {
    private readonly instance: AxiosInstance;

    private readonly abortControllerMap: Map<string, AbortController>;

    constructor(config: MyAxiosRequestConfig) {
        this.instance = axios.create(config);

        this.abortControllerMap = new Map();

        /* * 拦截器执行顺序，请求级请求拦截 -> 实例级 -> 全局级 -> 全局响应 -> 实例级 -> 请求级 */
        // 整个拦截器执行顺序是一个管道式(本质是数组)，从左往右，请求拦截到响应拦截。请求拦截从左边加入管道，响应拦截从右边加入管道。
        // 因此，请求拦截，最后加入管道的最先执行，响应拦截最先加入管道的最先执行。
        // 比如下面全局请求拦截中，执行顺序为 url加入map -> 添加token

        /* * 全局拦截 (所有 axios 实例都有的拦截器） */
        // 1. 添加 token
        this.instance.interceptors.request.use(config => {
            // const token = getToken()
            // if (token) config.headers.Authorization = `Bearer ${token}`
            return config;
        });

        // 2. axios http 错误处理
        this.instance.interceptors.response.use(undefined, error => {
            console.log("全局：错误处理");
            const { response } = error;
            // 1. 请求超时 && 网络错误单独判断，因为没有 response
            if (error.message.indexOf("timeout") !== -1) alert("请求超时！请您稍后重试");
            if (error.message.indexOf("Network Error") !== -1) alert("网络错误！请您稍后重试");
            // 2. 根据 http 服务器响应的错误状态码，做不同的处理
            if (response) {
                // checkStatus(response.status); // elmessage 提示错误
                switch (response.status) {
                    case 404:
                        alert("你所访问的资源不存在！");
                        break;
                    case 500:
                        alert("服务异常！");
                        break;
                    default:
                        alert("请求失败！");
                }
            }
            // 3. 服务器结果都没有返回(可能服务器错误可能客户端断网)，断网处理:也可以跳转到断网页面
            if (!window.navigator.onLine) alert("服务器错误或者无网络"); // router.replace("/500");
        });

        // 3. 取消请求
        // 为每个请求都生成一个 signal，并以 url 为 key 添加入 map
        this.instance.interceptors.request.use(
            config => {
                const controller = new AbortController();
                const url = config.url || "";
                config.signal = controller.signal;
                this.abortControllerMap.set(url, controller);
                return config;
            },
            (err: any) => err
        );
        // 响应完，从map中去除 url
        this.instance.interceptors.response.use(
            res => {
                const url = res?.config.url || "";
                this.abortControllerMap.delete(url);
                return res;
            },
            (err: any) => err
        );

        /* * 实例级别拦截（实例化 axios 实例时可以添加的拦截器）*/
        if (config.interceptors) {
            this.instance.interceptors.request.use(
                config.interceptors.reqInterceptorOnFulfilled,
                config.interceptors.reqInterceptorOnRejected
            );
            this.instance.interceptors.response.use(
                config.interceptors.resInterceptorOnFulfilled,
                config.interceptors.resInterceptorOnRejected
            );
        }
    }

    /**
     * Sends a request and returns a Promise that resolves with the response data.
     * @template TReqData - The type of the request body.
     * @template TResData - The type of the `data` field in the `{code, data}` response structure.
     * @template TResType - The type of the response structure. The default is `{code, data, msg}`.
     * @param {MyAxiosRequestConfig} [config] - The custom configuration for the request.
     * @returns {Promise<TResType>} - A Promise that resolves with the response data.
     * @throws {Error} - If the request fails.
     *
     * @example
     * // Sends a GET request and expects a response with a JSON object.
     * const response = await request<{id: number}, {name: string}>({
     *   method: 'GET',
     *   url: '/users/1',
     * });
     * console.log(response.name); // logs the name of the user
     */
    request<TReqData, TResData, TResType = ReturnType<TResData>>(
        config: MyAxiosRequestConfig<TReqData>
    ): Promise<TResType> {
        return new Promise<TResType>((resolve, reject) => {
            /* * 请求级别拦截（作用于具体某个请求的拦截器）*/
            // 手动执行请求拦截器
            if (config.interceptors?.reqInterceptorOnFulfilled) {
                config = config.interceptors.reqInterceptorOnFulfilled(config as InternalAxiosRequestConfig<TReqData>);
            }

            this.instance
                .request<any, AxiosResponse<TResType>>(config)
                .then(res => {
                    // 手动执行响应拦截器
                    if (config.interceptors?.resInterceptorOnFulfilled) {
                        res = config.interceptors.resInterceptorOnFulfilled(res);
                    }

                    resolve(res?.data);
                })
                .catch(err => {
                    if (config.interceptors?.resInterceptorOnRejected) {
                        err = config.interceptors.resInterceptorOnRejected(err);
                    }

                    reject(err);
                });
        });
    }

    /**
     * 发送 GET 请求
     * @template TReqData 请求数据类型
     * @template TResData 响应 data 数据类型
     * @template TResType 响应结构，默认为返回类型的 {code, data, msg}
     * @param {MyAxiosRequestConfig} config 请求配置
     * @returns {Promise} 响应数据的 Promise
     */
    get<TReqData, TResData, TResType = ReturnType<TResData>>(
        config?: MyAxiosRequestConfig<TReqData>
    ): Promise<TResType> {
        return this.request({ ...config, method: "GET" });
    }

    /**
     * 发送 post 请求
     * @template TReqData 请求数据类型
     * @template TResData 响应 data 数据类型
     * @template TResType 响应结构，默认为返回类型的 {code, data, msg}
     * @param {MyAxiosRequestConfig} config 请求配置
     * @returns {Promise} 响应数据的 Promise
     */
    post<TReqData, TResData, TResType = ReturnType<TResData>>(
        config: MyAxiosRequestConfig<TReqData>
    ): Promise<TResType> {
        return this.request({ ...config, method: "POST" });
    }

    patch<TReqData, TResData, TResType = ReturnType<TResData>>(
        config: MyAxiosRequestConfig<TReqData>
    ): Promise<TResType> {
        return this.request({ ...config, method: "PATCH" });
    }

    delete<TReqData, TResData, TResType = ReturnType<TResData>>(
        config?: MyAxiosRequestConfig<TReqData>
    ): Promise<TResType> {
        return this.request({ ...config, method: "DELETE" });
    }

    /**
     * 获取当前 axios 实例
     */
    getInstance(): AxiosInstance {
        return this.instance;
    }

    /**
     * 取消全部请求
     */
    cancelAllRequest() {
        for (const [, controller] of this.abortControllerMap) {
            controller.abort();
        }
        this.abortControllerMap.clear();
    }

    /**
     * 取消指定的请求
     * @param url 待取消的请求URL
     */
    cancelRequest(url: string | string[]) {
        const urlList = Array.isArray(url) ? url : [url];
        urlList.forEach(_url => {
            this.abortControllerMap.get(_url)?.abort();
            this.abortControllerMap.delete(_url);
        });
    }
}

export default LcRequest;
